/******************************************************************/
/*                                                                */
/*  Winpooch : Windows Watchdog                                   */
/*  Copyright (C) 2004  Benoit Blanchon                           */
/*                                                                */
/*  This program is free software; you can redistribute it        */
/*  and/or modify it under the terms of the GNU General Public    */
/*  License as published by the Free Software Foundation; either  */
/*  version 2 of the License, or (at your option) any later       */
/*  version.                                                      */
/*                                                                */
/*  This program is distributed in the hope that it will be       */
/*  useful, but WITHOUT ANY WARRANTY; without even the implied    */
/*  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR       */
/*  PURPOSE.  See the GNU General Public License for more         */
/*  details.                                                      */
/*                                                                */
/*  You should have received a copy of the GNU General Public     */
/*  License along with this program; if not, write to the Free    */
/*  Software Foundation, Inc.,                                    */
/*  675 Mass Ave, Cambridge, MA 02139, USA.                       */
/*                                                                */
/******************************************************************/

// module's interface
#include "DirWatcher.h"

// standart headers
#include <assert.h>
#include <shlobj.h>
#include <tchar.h>

// project's headers
#include "Language.h"
#include "Log.h"
#include "MainWindow.h"

// missing defines in shlobj.h
#define SHGFP_TYPE_CURRENT 0
#define SHGFP_TYPE_DEFAULT 1


#define MAX_MESSAGE_LEN		256
#define BUFFER_SIZE		1024

// filter used in ReadDirectoryChangesW
static DWORD dwFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
  FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE |
  /*FILE_NOTIFY_CHANGE_LAST_ACCESS | */ FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY ;

static STRID aActionToStrId[] =
  {
    0,			// 0 = no action
    _FILE_S_ADDED,	// 1 = FILE_ACTION_ADDED
    _FILE_S_REMOVED,	// 2 = FILE_ACTION_REMOVED
    _FILE_S_MODIFIED,	// 3 = FILE_ACTION_MODIFIED
    _FILE_S_RENAMED,	// 4 = FILE_ACTION_RENAMED_OLD_NAME
    _S_IS_THE_NEW_NAME,	// 5 = FILE_ACTION_RENAMED_NEW_NAME
  } ;

// real structure behind HDIRWATCHER
typedef struct {
  HWND		hwnd ;
  HANDLE	hThread ;
  TCHAR		szDirectory[MAX_PATH] ;
  BOOL		bSubDir ;   
} DIRWATCHER ;

// internal functions
DWORD WINAPI _DirWatcher_Thread (LPVOID pContext)
{
  DIRWATCHER   *p = (DIRWATCHER*) pContext ;
  TCHAR	        szPath[MAX_PATH] ;
  TCHAR		szMessage[MAX_MESSAGE_LEN] ;
  BYTE		aBuffer[BUFFER_SIZE] ;
  STRID		iActionStrId ;
  DWORD		dwBytesReturned ;
  HANDLE	hDir ;
  int		nLen ;
  FILE_NOTIFY_INFORMATION * pInfo ;  

  assert (p!=NULL) ;
  
  hDir = CreateFile (p->szDirectory,		// directory name
		     FILE_LIST_DIRECTORY,	// access mode
		     FILE_SHARE_READ|
		     FILE_SHARE_DELETE|
		     FILE_SHARE_WRITE,		// share mode
		     NULL,			// security descriptor
		     OPEN_EXISTING,		// how to create
		     FILE_FLAG_BACKUP_SEMANTICS,	// file attributes
		     NULL			// file with attributes to copy			    
		     ) ;

  if( hDir==INVALID_HANDLE_VALUE )
    {
      MainWnd_SendAlertf (p->hwnd, ALERT_ERROR, 
			 STR(_FAILED_TO_WATCH_DIRECTORY_S),
			 p->szDirectory) ;
      return 1 ;
    }  

  Log_Printf (STR(_START_WATCHING_DIRECTORY_S), p->szDirectory) ;
  
  while(1) {

    ReadDirectoryChangesW (hDir,	// directory handle
			   aBuffer,	// buffer address
			   BUFFER_SIZE,	// buffer size
			   p->bSubDir,	// watch subtree
			   dwFilter,	// notify filter
			   &dwBytesReturned,	// bytes returned
			   NULL,	// overlapped 
			   NULL		// completion routine
			   ) ;	

    // TODO : handle the case where the buffer is too small
        
    pInfo = (FILE_NOTIFY_INFORMATION*)aBuffer ;
    
    while(1) {

      // TODO : handle the case where pInfo->FileNameLength > buffer size
      
      // copy directory name
      _tcscpy (szPath, p->szDirectory) ;
      _tcscat (szPath, TEXT("\\")) ;    
     
      // copy file name
      nLen = _tcslen(szPath) ;
      assert (nLen+pInfo->FileNameLength/sizeof(TCHAR)<MAX_PATH) ;
      memcpy (szPath+nLen, pInfo->FileName, pInfo->FileNameLength) ;
      szPath[nLen+pInfo->FileNameLength/sizeof(TCHAR)] = 0 ;
      
      // get the string that correspond to action
      if( pInfo->Action>0 && pInfo->Action<=5 )
	iActionStrId = aActionToStrId[pInfo->Action] ;
      else
	iActionStrId = _ALTERATION_TO_S ;

      // format message
      wsprintf (szMessage, STR(iActionStrId), szPath) ;

      Log_Print (szMessage) ;
      
      MainWnd_SendAlert (p->hwnd, ALERT_DETECTION, szMessage) ;

      if( ! pInfo->NextEntryOffset ) break ;

      pInfo = (FILE_NOTIFY_INFORMATION*)( (BYTE*)pInfo + pInfo->NextEntryOffset ) ;      
    } 
  }
  
  return 0 ; // this will never happen
}




HDIRWATCHER DirWatcher_NewByPath (HWND hwnd, LPCTSTR szDirectory, BOOL bSubDir)
{
  DIRWATCHER * p ;
  DWORD	dwThreadId ;
  
  p = malloc (sizeof(DIRWATCHER)) ;

  p->hwnd = hwnd ;
  p->bSubDir = bSubDir ;
  lstrcpy (p->szDirectory, szDirectory) ;  

  p->hThread = CreateThread (NULL, 0, _DirWatcher_Thread, p, 0, &dwThreadId) ;
  
  return p ;
}

HDIRWATCHER DirWatcher_NewByCSIDL (HWND hwnd, int nFolder, BOOL bSubDir) 
{
  TCHAR szPath[MAX_PATH] ;
  
  if( FAILED(SHGetFolderPath (NULL, nFolder, NULL, SHGFP_TYPE_CURRENT,  szPath) ) )
    return NULL ;

  return DirWatcher_NewByPath (hwnd, szPath, bSubDir) ;
}


VOID DirWatcher_Delete (HDIRWATCHER h)
{
  DIRWATCHER * p = (DIRWATCHER*)h ;

  if( p ) 
    {
      Log_Printf (STR(_STOP_WATCHING_DIRECTORY_S), p->szDirectory) ;
      
      // kill the thread
      // i didn't find a better solution
      TerminateThread (p->hThread, 2) ;
      CloseHandle (p->hThread) ;

      free (p) ;
    }
}
